/* 
	Copyright (C) 2005 Michael S. Finger
	& 2005 Everquest Emulator Team

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; version 2 of the License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY except by those people which sell it, which
	are required to give you total support for your newly bought product;
	without even the implied warranty of MERCHANTABILITY or FITNESS FOR
	A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
//#include "StdAfx.h"
#include "../common/debug.h"
#include <string>
#include <iostream>
#include <map>
#include <pcap.h>
#include <signal.h>
#include "pcvars.h"
#ifdef WIN32
	#include <conio.h>
	#include <pcap.h>
	#include <windows.h>
	#include <process.h>
#else
	#include <sys/socket.h>
	#include <sys/resource.h>
	#include <arpa/inet.h>
#endif
#include "EQStreamPair.h"
#include "../common/misc.h"
#include "../common/files.h"
#include "protocol.h"
#include "../common/opcodemgr.h"
#include "StreamPairManager.h"
#include "PluginManager.h"
#include "PacketHandler.h"

using namespace std;

void GetAdapterFromList(void* device, int index);

#ifdef FREEBSD
//FatherNitwit: freebsd's built in pcap dosent have pcap_next_ex
//so heres my non-thread safe hack version of it


struct __bsd_pcap_crap_struct {
	struct pcap_pkthdr *header;
	const u_char **data;
};

static void __bsd_pcap_crap_handler(unsigned char *u, const pcap_pkthdr *h, const unsigned char *pkt) {
	struct __bsd_pcap_crap_struct *s = (struct __bsd_pcap_crap_struct *) u;
	//copy in the header info
	*s->header = *h;
	//get a pointer to the body
	*s->data = pkt;
}

int pcap_next_ex(pcap_t *p, struct pcap_pkthdr **pkt_header, const u_char **pkt_data);
int pcap_next_ex(pcap_t *p, struct pcap_pkthdr **pkt_header, const u_char **pkt_data) {
	struct __bsd_pcap_crap_struct s;
	
	//this is what makes this not thread safe.
	static pcap_pkthdr header;
	s.header = &header;
	*pkt_header= &header;
	
	s.data = pkt_data;
	
	return(
		pcap_dispatch(p, 1, __bsd_pcap_crap_handler, (unsigned char *) &s)
	);
}

#endif



#ifndef WIN32
	//unix specific stuff
	Mutex MSignal;
#endif

//char errbuf[PCAP_ERRBUF_SIZE];
PCMode mode = noMode;
pcap_t *handle = NULL;
char pcapdevice[256];
char pcapDeviceDesc[256];	// Device Human readable name
bool OpcodeLoadFailed = false;
bool DoLoop = false;
bool ProcessThreadRunning = false;
StreamPairManager stream_mgr(false);
PluginManager *plugins = NULL;
int debug=0;
char sniffhost[255]			= "";

//privacy stuff:
bool privacy_mode = false;
char privacy_name[64] = "";
char _zero_buffer[64] = "";


void handle_packet(unsigned char* x, const struct pcap_pkthdr* header, const unsigned char* data)
{
	map<string,EQStreamPair *>::iterator stream_itr;
	ETHERNET_FRAME	*eh;
	IP_HEADER		*iph;
	UDP_HEADER		*uh;
	uchar			*udp_data;
	int32			datalen = 0;
	int32			index = 0;
	
	eh = (ETHERNET_FRAME*) &data[index];
	index += sizeof(ETHERNET_FRAME);

	if (ntohs(eh->FrameType) != ETHERNET_FRAME_TYPE_IP) {
		#if DEBUGLEVEL >= 2
		cout << "Got a non-IP packet" << endl;
		#endif
		return;
	}
	
	iph = (IP_HEADER*) &data[index];
	index += sizeof(IP_HEADER);
	uh = (UDP_HEADER*) &data[index];
	index += sizeof(UDP_HEADER);
	udp_data = const_cast<unsigned char *>(&data[index]);
	datalen = ntohs(uh->length) - sizeof(UDP_HEADER);
	
	#if DEBUG_EtherPACKET >= 5
		struct in_addr	src, dst;
		src.s_addr = iph->src;
		dst.s_addr = iph->dest;
		cout << "Ether: " << inet_ntoa(src) << ":" << ntohs(uh->src_port) << " -> ";
		cout << inet_ntoa(dst) << ":" << ntohs(uh->dest_port) << " size: " << buflen << endl;
		#if DEBUG_EtherPACKET >= 9
			DumpPacket(buf, buflen);
		#endif
	#endif
	
	if (datalen == 0) {
		#if DEBUG_EtherPACKET >= 3
		cout << "Dropping Ether packet: datalen == 0" << endl;
		#endif
		return;
	}

	uint16 sport = ntohs(uh->src_port);
	uint16 dport = ntohs(uh->dest_port);
	if (sport <= 1024 || dport <= 1024) {
		#if DEBUG_EtherPACKET >= 3
		cout << "Dropping Ether packet: port <= 1024" << endl;
		#endif
		return;
	}
	/*if (!(iph->src == sniffIP || iph->dest == sniffIP)) {
		#if DEBUG_EtherPACKET >= 3
		cout << "Dropping Ether packet: ip != sniffIP" << endl;
		#endif
		return;
	}*/
	
	unsigned long sip = (unsigned long) iph->src;
	unsigned long dip = (unsigned long) iph->dest;


	stream_mgr.Process(sip, sport, dip, dport, header->ts, udp_data, datalen);
}


void CatchSignal(int);
void Exit();

void SetupGlobals() {
	if (signal(SIGINT, CatchSignal) == SIG_ERR) {
		cerr << "Could not set signal handler" << endl;
		Exit();
	}
	
	memset(_zero_buffer, 0, 64);
	
	strcpy(pcapdevice, "");
	strcpy(pcapDeviceDesc, "");

//	load_opcode_names();
	
	OpcodeLoadFailed = false;
	if(!stream_mgr.LoadOpcodes("opcodes.conf")) {
		cerr << "Unable to load opcodes map. Names will not be resolved." << endl;
		OpcodeLoadFailed = true;
	} else {
		printf("Opcodes file %s successfully loaded.\n", OPCODES_FILE);
	}

}

void SetupCollectEnvironment() {
}


bool SetupLiveCollect() {
	struct bpf_program filter;
	char errbuf[PCAP_ERRBUF_SIZE];
	memset(errbuf, 0, sizeof(errbuf));
	char filter_app[200];
	bpf_u_int32 mask;
	bpf_u_int32 net;
    struct in_addr	in;

#if (defined(WIN32) && (!defined(_DEBUG)))
	SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
#endif
	memset(filter_app, 0, sizeof(filter_app));
//	sprintf(filter_app, "udp and host %s", sniffhost);
	sprintf(filter_app, "udp");

    GetAdapterFromList( pcapdevice, atoi(pcapdevice) );

	if (strlen(pcapdevice) == 0) {
		cout << "Error: pcap_lookupdev: '" << errbuf << "'" << endl;
		Exit();
	}
	pcap_lookupnet(pcapdevice, &net, &mask, errbuf);

net = 0;
#ifdef WIN32
	in.s_addr = ntohl(net);
#else
	in.s_addr = net;
#endif

	cout << "Sniffing on '";
	cout << ((char*)pcapDeviceDesc);
	cout << "', ";

	cout << "net=" << inet_ntoa(in) << ", target=" << sniffhost;
	//in.s_addr = eqpsl.sniffIP;
	//cout << " (" << inet_ntoa(in) << ")";
	cout << endl;
	
	if(privacy_mode) {
		cout << "Privacy mode enabled, privacy name: '" << privacy_name << "'" << endl;
	}
	
	handle = pcap_open_live(pcapdevice, 8192, 1, 100, errbuf);

	if (handle == NULL) {
		cout << "Error: pcap_open_live: '" << errbuf << "'" << endl;
		return(false);
	}
	pcap_compile(handle, &filter, filter_app, 0, net);
	if (pcap_setfilter(handle, &filter)) {
		cerr << "Error: pcap_setfilter("<<filter_app<<"): " << pcap_geterr(handle) << endl;
		return(false);
	}
	return(true);
}

bool SetupFileCollect(const char *filename) {
	char errbuf[PCAP_ERRBUF_SIZE];
	if ((handle=pcap_open_offline(filename,errbuf))==NULL) {
		cerr << "pcap_open_offline() on '" << filename << "' failed: " << errbuf << endl;
		return(false);
	}
	cout << "Successfully opened offline file: " << filename << endl;
	return(true);
}

void ClearCollectEnvironment() {
	
	cout << "Cleaning up collecting environment." << endl;
	
	DoLoop = false;
	while (ProcessThreadRunning) {
		Sleep(10);
	}
	
	stream_mgr.CloseAll();
	
}

void Exit() {
	ClearCollectEnvironment();
	
	safe_delete(plugins);
}

ThreadReturnType ProcessThread(void *tmp);

void CatchSignal(int sig_num) {
#ifndef WIN32
	MSignal.lock();
#endif
	//cerr << "Got signal: " << sig_num << endl;
	printf("Got signal %d\n", sig_num);
	DoLoop = false;
#ifndef WIN32
	MSignal.unlock();
#endif
}


void StartCollecting() {
#ifdef WIN32
	_beginthread(ProcessThread, 0, 0);
	//SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
#else
	pthread_t thread;
	pthread_create(&thread, NULL, &ProcessThread, NULL);
	setpriority(PRIO_PROCESS, getpid(), 7);
#endif
	
	DoLoop = true;
//	uploading=false;
	
	
	stream_mgr.CloseAll();	
	
	if(mode == instantFileMode) {
		//just run through the file, not caring about time
		cout << "Starting instant file playback." << endl;
		while (DoLoop) {
			//read a number of packets, then distpatch them. Assuming
			//that we dont care if the other thread picks 
			if(pcap_dispatch(handle, 100, handle_packet, NULL) < 1) {
				break;	//done processing or error... stop either way
			}
			//process them
			stream_mgr.CheckQueues();
		}
	} else if(mode == timedFileMode) {
		cout << "Starting real-time file playback." << endl;
		
		struct pcap_pkthdr *header;
#ifdef WIN32
		u_char *frame;
#else
		const u_char *frame;
#endif
		
		//read the first packet...
		if(pcap_next_ex(handle, &header, (const u_char**)&frame) < 1) {
			printf("Unable to read first packet.\n");
		} else {
			uint32 delta;
			struct timeval lasttime, curtime;
			
			//start with a base time of the first packet
			lasttime.tv_sec = header->ts.tv_sec;
			lasttime.tv_usec = header->ts.tv_usec;
			
			do {
				//sleep the delay until this packet is due
				curtime = header->ts;
				if(lasttime.tv_usec > curtime.tv_usec) {
					//if our usec wrapped, undo that effect
					curtime.tv_usec += 1000000;
					curtime.tv_sec -= 1;
				}
				//get our delta in ms
				delta = 1000 * (curtime.tv_sec - lasttime.tv_sec);
				delta += (curtime.tv_usec - lasttime.tv_usec) / 1000;
				//do our sleep
				if(delta > 0) {
					if(delta > 10000) {
						printf("Long sleep for %d ms\n", delta);
					}
					Sleep(delta);
				}
				//set our last time
				lasttime = curtime;
				
				//process the packet
				handle_packet(NULL, header, frame);
			} while(DoLoop && pcap_next_ex(handle, &header, (const u_char**)&frame) == 1); 
		}
	} else if(mode == liveCollectMode) {
		cout << "Starting live network collect." << endl;
		while (DoLoop) {
			//changed loop to dispatch some packets and then give up and check DoLoop, then
			//repeat, to allow for interactive things to turn collecting on and off
			//the 100 is arbitrary
			pcap_dispatch(handle, 100, handle_packet, NULL);
		}
	} else {
		cout << "Unknown capture mode. Unable to do anything useful." << endl;
	}
	
	//process anything left in the queues one last time
	stream_mgr.CheckQueues();
	
	
	DoLoop = false;
	
	//wait for the processing thread to finish up
	while (ProcessThreadRunning) {
		Sleep(1);
	}
}

ThreadReturnType ProcessThread(void *tmp) {
	ProcessThreadRunning = true;
	DoLoop = true;
	while (DoLoop) {
#ifdef WIN32
/*		if(kbhit()){
			char input=getch();
			if(input==3)
				CatchSignal(2);
			else if(input==21){
				if(!uploading){
					uploading=true;
					_beginthread(DBUploadThread, 0, 0);
					//SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
				}
				else
					cout << "You are already uploading items!!\n";

			}
			else if(input==12){
				uploading=false;
				cout << "Cleared upload flag, you may now upload items...\n";
			}

			input=0;
		}*/
#endif
		Timer::SetCurrentTime();
		
		timeout_manager.CheckTimeouts();
		stream_mgr.CheckQueues();
		
		Sleep(10);
	}
	//im not 100% sure why we closed this here:
	//pcap_close(handle);
	ProcessThreadRunning = false;
#ifndef WIN32
	return 0;
#endif
}

void GetAdapterList() {
	char ebuf[PCAP_ERRBUF_SIZE];
	//dunno what the return of this thing is:
	pcap_if_t *list, *olist;
	pcap_findalldevs(&list, ebuf);
	olist = list;
	int cur = 0;
	while(list != NULL) {
	    if( list->description != NULL )
		    printf( "%d = %s\n", cur, list->description );
		else
		    printf( "%d = %s\n", cur, list->name );
		list = list->next;
		++cur;
	}
	pcap_freealldevs(list);

}

void GetAdapterFromList(void* device, int index) {
	char ebuf[PCAP_ERRBUF_SIZE];
	//dunno what the return of this thing is:
	pcap_if_t *list, *olist;
	pcap_findalldevs(&list, ebuf);
	olist = list;
	bool found=false;
	int cur=0;
	while(list != NULL) {
		if(cur == index) {
			found=true;
			break;
		}
		cur++;
		list = list->next;
	}
	if( found )
	{
	    if( list->name != NULL )
	    {
		    strcpy( pcapdevice, list->name );
		    strcpy( pcapDeviceDesc, list->name );
		}
		if( list->description != NULL )
		    strcpy( pcapDeviceDesc, list->description );
		    
	}
	pcap_freealldevs(list);
}

#ifdef WIN32
bool CheckIsWin95() {
	DWORD dwVersion = GetVersion();
	DWORD dwWindowsMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));

	if (dwVersion >= 0x80000000 && dwWindowsMajorVersion >= 4)			// Windows '95
		return true;
	else
		return false;
}


#endif








